package br.ufrgs.dbproject.sax;

import java.sql.SQLException;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;

import br.ufrgs.dbproject.PathBuilder;
import br.ufrgs.dbproject.dsl.sqlbatch.SqlInsertionBatch;
import br.ufrgs.dbproject.idgenerator.dewey.DeweyID;
import br.ufrgs.dbproject.idgenerator.dewey.DeweyIDGenerator;
import br.ufrgs.dbproject.pathcache.CacheEntry;

/**
 * SAX Parser which maps XML documents to relational databases using Dewey
 * approach.
 * 
 * @author diego
 */
public class DeweySaxParserHandler extends DefaultHandler2 {

	public static final String DOCUMENTS = "documents";
	public static final String PATHS = "paths";
	public static final String ELEMENTS = "elements";
	public static final String ATTRIBUTES = "attributes";
	public static final String TEXTS = "texts";

	public static final String COLUMN_ATTRIBUTE_VALUE = "attribute_value";
	public static final String COLUMN_ATTRIBUTE_NAME = "attribute_name";
	public static final String COLUMN_DOCUMENT_NAME = "document_name";
	public static final String COLUMN_ELEMENT_ID = "element_id";
	public static final String COLUMN_ID = "id";
	public static final String COLUMN_DOC_ID = "doc_id";
	public static final String COLUMN_TAG_NAME = "tag_name";
	public static final String COLUMN_TEXT_VALUE = "text_value";
	public static final String DOCUMENT_NAME = "document";
	public static final String COLUMN_PATH_ID = "path_id";
	public static final String COLUMN_PATH = "path";

	/**
	 * Used to create XPath on demand from parsed XML elements
	 */
	private PathBuilder pathBuilder;

	/**
	 * Generates Dewey IDs based on a parent ID or a sibling ID
	 */
	private DeweyIDGenerator idGenerator;

	/**
	 * Used to create a batch of SQL insert commands.
	 */
	private SqlInsertionBatch commandsBatch;

	/**
	 * Used to build text elements within tag elements.
	 */
	private TextBuilder textBuilder;
	
	/**
	 * The XML document ID used as primary key in the relational mapped tables
	 */
	private Integer documentID;

	/**
	 * Creates an instance of {@link DeweySaxParserHandler} with a specific
	 * {@link SqlInsertionBatch}.
	 * 
	 * @param commandsBatch
	 *            {@link SqlInsertionBatch} used to execute a batch of insert
	 *            commands.
	 */
	public DeweySaxParserHandler(SqlInsertionBatch commandsBatch, DeweyIDGenerator idGenerator) {

		this.idGenerator = idGenerator;
		this.commandsBatch = commandsBatch;

		this.pathBuilder = new PathBuilder();
		this.textBuilder = new TextBuilder();
		this.documentID = 1;
	}

	/**
	 * Creates insertion commands for element attributes and add them to the
	 * {@link SqlInsertionBatch}
	 * 
	 * @param attributes
	 *            a XML element's attributes
	 * @param generatedElementID
	 *            ID of the element which holds the attributes
	 */
	private void addAttributesInsertCommandsToBatch(Attributes attributes) {

		for (int i = 0; i < attributes.getLength(); i++) {

			String name = attributes.getLocalName(i);
			String value = attributes.getValue(i);
			if (value.contains("'")) {
				value = value.replace("'", "''");
			}
			CacheEntry cacheEntry = pathBuilder.getLastCachedEntry();
			
			commandsBatch.forTable(ATTRIBUTES).insertIntoColumns(COLUMN_DOC_ID, COLUMN_ELEMENT_ID, COLUMN_PATH_ID, COLUMN_ATTRIBUTE_NAME, COLUMN_ATTRIBUTE_VALUE).theseValues(documentID.toString(), idGenerator.getContext().getLastSavedID().toString(), cacheEntry.getKey().toString(), name, value);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
	 */	
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		textBuilder.write(ch, start, length);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startDocument()
	 */
	public void startDocument() throws SAXException {
		commandsBatch.forTable(DOCUMENTS).insertIntoColumns(COLUMN_ID, COLUMN_DOCUMENT_NAME).theseValues(documentID.toString(), DOCUMENT_NAME);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#endDocument()
	 */
	public void endDocument() throws SAXException {
		try {
			commandsBatch.execute();

		} catch (SQLException e) {
			throw new RuntimeException("An error has occur when attempting to execute commands batch on the database.", e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
	 * java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

		textBuilder.activate();

		DeweyID generatedID = idGenerator.generateNext();
		CacheEntry entry = pathBuilder.append(localName);

		if (entry.wasFetched()) {
			commandsBatch.forTable(PATHS).insertIntoColumns(COLUMN_ID, COLUMN_DOC_ID, COLUMN_PATH).theseValues(entry.getKey().toString(), documentID.toString(), entry.getPath().toString());
		}

		commandsBatch.forTable(ELEMENTS).insertIntoColumns(COLUMN_ID, COLUMN_DOC_ID, COLUMN_PATH_ID, COLUMN_TAG_NAME).theseValues(generatedID.toString(), documentID.toString(), entry.getKey().toString(), localName);

		if (attributes.getLength() > 0) {
			addAttributesInsertCommandsToBatch(attributes);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
	 * java.lang.String, java.lang.String)
	 */
	public void endElement(String namespaceURI, String localName, String qName) throws SAXException {

		idGenerator.getContext().forgetLastSavedID();
		pathBuilder.removeTail();
		
		if (!textBuilder.isBufferEmpty()) {
			String textElement = textBuilder.getText();
			commandsBatch.forTable(TEXTS).insertIntoColumns(COLUMN_DOC_ID, COLUMN_ELEMENT_ID, COLUMN_TEXT_VALUE).theseValues(documentID.toString(), idGenerator.getContext().getLastSavedID().toString(), textElement);
			textBuilder.deactivate();
		}
		
	}

	public void setPathBuilder(PathBuilder pathBuilder) {
		this.pathBuilder = pathBuilder;
	}

	public void setTextBuilder(TextBuilder textBuilder) {
		this.textBuilder = textBuilder;
	}

	public void setDocumentID(Integer documentID) {
		this.documentID = documentID;
	}
}
